﻿/* Copyright (c) 2011-2012, Zetatron Consulting
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

* Neither the name of Zetatron Consulting nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WorldServer.Models;
using AionCxxLibrary.Enums;
using AionCxxLibrary.Utilities;

namespace WorldServer.Services
{
    public class LevelService
    {
        public static Dictionary<int, AionObject> Objects = new Dictionary<int, AionObject>();
        static Dictionary<int, Character> Characters = new Dictionary<int, Character>();

        /// <summary>
        /// Internal variable. Do not modify.
        /// </summary>
        static int LevelInstanceGuidRef = 5;

        /// <summary>
        /// Calculate a new Instance GUID.
        /// </summary>
        /// <returns>The newly created instance GUID.</returns>
        static int NextInstanceGUID() { return LevelInstanceGuidRef++; }

        /// <summary>
        /// This dictionary holds all existing level instances actually living on the server.
        /// Sorted by Instance GUID.
        /// </summary>
        static Dictionary<int, LevelInstance> AliveInstances = new Dictionary<int, LevelInstance>();

        /// <summary>
        /// Register a new object in LevelService.
        /// </summary>
        /// <param name="obj">Object to register.</param>
        public static void AddObject(AionObject obj)
        {
            Objects.Add(obj.GetGUID(), obj);
            if (obj is Character)
            {
                Character c = obj as Character;
                Characters.Add(c.GetGUID(), c);
            }
        }

        /// <summary>
        /// Unregister an object in LevelService (player disconnected, object despawned)
        /// </summary>
        /// <param name="obj">Object to unregister.</param>
        public static void RemoveObject(AionObject obj)
        {
            Objects.Remove(obj.GetGUID());
            if (obj is Character)
            {
                Character c = obj as Character;
                Characters.Remove(c.GetGUID());
            }
        }

        /// <summary>
        /// Find a Character in the World.
        /// </summary>
        /// <param name="characterId"></param>
        /// <returns></returns>
        public static Character GetCharacter(int characterId)
        {
            if (!Characters.ContainsKey(characterId))
                return null;
            return Characters[characterId];
        }

        public static Character GetCharacter(string name)
        {
            return Characters.Values.ToList().Find(c => c.Template.Name == name);
        }

        public static void InitializeLevels()
        {
            Log.Info("Initializing Levels ...");
            CreateLevelInstance(Levels.Altgard);
            CreateLevelInstance(Levels.Beluslan);
            CreateLevelInstance(Levels.Brusthonin);
            CreateLevelInstance(Levels.Eltnen);
            CreateLevelInstance(Levels.Gelkmaros);
            CreateLevelInstance(Levels.Heiron);
            CreateLevelInstance(Levels.Inggison);
            CreateLevelInstance(Levels.Ishalgen);
            CreateLevelInstance(Levels.Morheim);
            CreateLevelInstance(Levels.Pandaemonium);
            CreateLevelInstance(Levels.Poeta);
            CreateLevelInstance(Levels.Reshanta);
            CreateLevelInstance(Levels.Sanctum);
            CreateLevelInstance(Levels.Silentera);
            CreateLevelInstance(Levels.Theobomos);
            CreateLevelInstance(Levels.Verteron);
            Log.Info(AliveInstances.Count + " Level Instances populated.");
        }

        /// <summary>
        /// Gets the LevelInstance given a WorldID and a ChannelID.
        /// If Recreate is set to 'true', the instance will be created if it doesn't exist. Useful for relog in same instance after server restart.
        /// </summary>
        /// <param name="worldId"></param>
        /// <param name="channel"></param>
        /// <param name="recreate"></param>
        /// <returns></returns>
        public static LevelInstance GetLevelInstance(int worldId, int channel, bool recreate)
        {
            LevelInstance instance = AliveInstances.Values.ToList().Find(i => i.WorldID == worldId && i.ChannelID == channel);
            if (instance == null && recreate)
                instance = CreateLevelInstance(worldId);
            return instance;
        }

        static LevelInstance CreateLevelInstance(Levels lvl)
        {
            return CreateLevelInstance((int)lvl);
        }

        public static LevelInstance CreateLevelInstance(int worldId)
        {
            lock (AliveInstances)
            {
                // 1. Obtain a channel for this instance
                ushort channel = 1;
                List<LevelInstance> instances = AliveInstances.Values.ToList();
                while (instances.Find(i => i.WorldID == worldId && i.ChannelID == channel) != null)
                    channel++;

                // 2. Generate new GUID
                int guid = NextInstanceGUID();

                // 3. Create the instance
                LevelInstance instance = new LevelInstance(guid, worldId, channel);

                // 4. Add to AliveInstances
                AliveInstances.Add(guid, instance);

                Log.Debug("LevelInstance created - " + worldId + " | chan " + channel + " | guid " + guid);

                return instance;
            }
        }

        /// <summary>
        /// Update a Character Position after a client move sequence.
        /// </summary>
        /// <param name="c">Moving character</param>
        /// <param name="x">Magnitude X</param>
        /// <param name="y">Magnitude Y</param>
        /// <param name="z">Magnitude Z</param>
        /// <param name="heading">Direction</param>
        static void UpdatePosition(Character c, float x, float y, float z, byte heading)
        {
            c.Position.x = x;
            c.Position.y = y;
            c.Position.z = z;
            c.Position.h = heading;
        }

        /// <summary>
        /// Called each time a character moves. It should only be called from CPkt_CharacterMove.
        /// </summary>
        /// <param name="c"></param>
        /// <param name="moveType"></param>
        /// <param name="x1"></param>
        /// <param name="y1"></param>
        /// <param name="z1"></param>
        /// <param name="x2"></param>
        /// <param name="y2"></param>
        /// <param name="z2"></param>
        /// <param name="heading"></param>
        public static void Move(Character c, MovementType moveType, float x1, float y1, float z1, float x2, float y2, float z2, byte heading)
        {
            Log.Debug("Character '" + c.Template.Name + "', " + moveType.ToString() + " | Pos = " + x1 + "/" + y1 + "/" + z1 + " | Pos2 = " + x2 + "/" + y2 + "/" + z2);

            // Trigger all required events
            c.OnMove();

            // Update character position in its object.
            UpdatePosition(c, x1, y1, z1, heading);
            
            // Recalculate NearbyObjects.
            c.LevelInstance.RecalculateNearbyObjects(c);
        }

    }
}
